Crate culpa

source ·
Expand description

Annotates a function that “throws” a Result.

Inside functions tagged with either throws or try_fn, you can use ? and the throw! macro to return errors, but you don’t need to wrap the successful return values in Ok.

Using this syntax, you can write fallible functions almost as if they were infallible. Every time a function call would return a Result, you “re-raise” the error using ?, and if you wish to raise your own error, you can return it with the throw! macro.

The difference between throws and try_fn is in the function signature, with throws you write the signature as if it were infallible too, it will be transformed into a Result for you. With try_fn you write the signature as normal and only the body of the function will be transformed.

Example

use std::io::{self, Read};

use culpa::{throw, throws, try_fn};

#[throws(io::Error)]
fn check() {
    let mut file = std::fs::File::open("The_House_of_the_Spirits.txt")?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;

    if !text.starts_with("Barrabas came to us by sea, the child Clara wrote") {
        throw!(io::Error::from_raw_os_error(22));
    }

    println!("Okay!");
}

#[try_fn]
fn check_as_try_fn() -> std::io::Result<()> {
    let mut file = std::fs::File::open("The_House_of_the_Spirits.txt")?;
    let mut text = String::new();
    file.read_to_string(&mut text)?;

    if !text.starts_with("Barrabas came to us by sea, the child Clara wrote") {
        throw!(io::Error::from_raw_os_error(22));
    }

    println!("Okay!");
}

throws Default Error Type

The throws macro supports a “default error type” - if you do not pass a type to the macro, it will use the type named Error in the current scope. So if you have defined an error type in the module, that will be the error thrown by this function.

You can access this feature by omitting the arguments entirely or by passing _ as the type.

Example

use culpa::throws;

// Set the default error type for this module:
use std::io::Error;

#[throws]
fn print() {
   let file = std::fs::read_to_string("my_file.txt")?;
   println!("{}", file);
}

Throwing as an Option

This syntax can also support functions which return an Option instead of a Result. To use this with throws pass as Option as the argument in place of the error type, to use it with try_fn just put it as the return type like normal

In functions that return Option, you can use the throw!() macro without any argument to return None.

Example

#[culpa::throws(as Option)]
fn example<T: Eq + Ord>(slice: &[T], needle: &T) -> usize {
    if !slice.contains(needle) {
        culpa::throw!();
    }
    slice.binary_search(needle).ok()?
}

#[culpa::try_fn]
fn example_as_try_fn<T: Eq + Ord>(slice: &[T], needle: &T) -> Option<usize> {
    if !slice.contains(needle) {
        culpa::throw!();
    }
    slice.binary_search(needle).ok()?
}

Other Try types

The ? syntax in Rust is controlled by a trait called Try, which is currently unstable. Because this feature is unstable and I don’t want to maintain compatibility if its interface changes, this crate currently only works with two stable Try types: Result and Option. However, its designed so that it will hopefully support other Try types as well in the future.

It’s worth noting that Try also has some other stable implementations: specifically Poll. Because of the somewhat unusual implementation of Try for those types, this crate does not support throws syntax on functions that return Poll (so you can’t use this syntax when implementing a Future by hand, for example). I hope to come up with a way to support Poll in the future.

Macros

Attribute Macros

  • Annotates a function that “throws” a Result.
  • Annotates a function that implicitly wraps a try block.